/**
 * Copyright 2015年1月30日 Wang Zheng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Wang Zheng ufo5260987423@163.com
 *
 */
package com.ufo5260987423.graphDatabase.core.GAO;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.ufo5260987423.graphDatabase.core.DAO.AbstractFactory;
import com.ufo5260987423.graphDatabase.core.DAO.Node;
import com.ufo5260987423.graphDatabase.core.DAO.NodeAttributeName;
import com.ufo5260987423.graphDatabase.core.DAO.NodeAttributeNameFactory;
import com.ufo5260987423.graphDatabase.core.DAO.NodeAttributeValue;
import com.ufo5260987423.graphDatabase.core.DAO.NodeAttributeValueFactory;
import com.ufo5260987423.graphDatabase.core.DAO.NodeFactory;
import com.ufo5260987423.graphDatabase.core.sqlTypes.SqlChars;
import com.ufo5260987423.graphDatabase.core.sqlTypes.SqlInteger;

/**
 * @ClassName: GraphNodeFactory
 * @Description: TODO
 * @author Wang Zheng ufo5260987423@163.com
 * @date 2015年3月27日 下午8:20:06
 * 
 */
public class GraphNodeFactory {
	private GraphEdgeFactory graphEdgeFactory;

	private NodeFactory nodeFactory;
	private NodeAttributeNameFactory nodeAttributeNameFactory;
	private NodeAttributeValueFactory nodeAttributeValueFactory;

	public GraphNodeFactory() {
		this.setGraphEdgeFactory(new GraphEdgeFactory());
		this.setNodeFactory(new NodeFactory());
		this.setNodeAttributeNameFactory(new NodeAttributeNameFactory());
		this.setNodeAttributeValueFactory(new NodeAttributeValueFactory());
	}

	public Boolean graphNodeisExist(Integer graphNodeId) {
		return this.getNodeFactory().isExist(new SqlInteger<Integer>((short) 8, graphNodeId));
	}

	public Boolean graphAttributeNameIsExist(String graphAttributeName) {
		return null != this.getNodeAttributeNameFactory().getBy(graphAttributeName);
	}

	public Boolean graphAttributeValueIsExist(Integer nodeId, String nodeAttributeName) {
		return null != this.getNodeAttributeValueFactory().getBy(nodeId,
				this.getNodeAttributeNameFactory().getBy(nodeAttributeName).getId().getContent());
	}

	public Long getBiggestId() {
		return new Long(this.getNodeFactory().getBiggestId());
	}

    //Attention:this method is just for test.
	public List<GraphNode> searchByNodeAttribute(String key,String value){
        List<GraphNode> result=new ArrayList<>();
		NodeAttributeName name=this.getNodeAttributeNameFactory().getBy(key);
		if(null==name)
			return null;

        Integer keyId=name.getId().getContent();
        try {
            for(NodeAttributeValue attributeValue:
                this.getNodeAttributeValueFactory()
                    .select("where " + AbstractFactory.NODE_ATTRIBUTE_NAME_ID
                    + "=" + keyId + " and " + AbstractFactory.NODE_ATTRIBUTE_VALUE
                    + "='" + value + "'"))
                result.add(this.getGraphNodeBy(attributeValue.getNodeId().getContent()));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return result;
	}

	public Boolean deleteGraphNode(GraphNode graphNode) {
		Boolean result = true;
		if (null == graphNode.getGraphNodeId())
			return false;
		for (GraphEdge graphEdge : graphNode.getFromThisNodeEdges().values())
			result = result ? this.getGraphEdgeFactory().deleteGraphEdge(graphEdge) : false;
		for (GraphEdge graphEdge : graphNode.getToThisNodeEdges().values())
			result = result ? this.getGraphEdgeFactory().deleteGraphEdge(graphEdge) : false;

		for (String key : graphNode.getAttributeMap().keySet())
			if (result) {
				NodeAttributeName nodeAttributeName = this.getNodeAttributeNameFactory().getBy(key);
				Integer keyId = null == nodeAttributeName ? null : nodeAttributeName.getId().getContent();
				if (null != keyId)
					try {
						this.getNodeAttributeValueFactory().delete(
								this.getNodeAttributeValueFactory().getBy(graphNode.getGraphNodeId(), keyId));
					} catch (SQLException e) {
						result = false;
						e.printStackTrace();
					}
			}
		Node node = new Node();
		node.setId(new SqlInteger<Integer>((short) 8, graphNode.getGraphNodeId()));
		try {
			this.getNodeFactory().delete(node);
		} catch (SQLException e) {
			result = false;
			e.printStackTrace();
		}
		return result;
	}

	public Boolean saveGraphEdges(GraphNode graphNode) {
		Boolean result = true;
		List<Long> list = new ArrayList<Long>();
		for (GraphEdge graphEdge : graphNode.getFromThisNodeEdges().values()) {
			result = result ? null!=this.getGraphEdgeFactory().saveGraphEdge(graphEdge) : false;
			list.add(graphEdge.getEdgeId());
		}
		for (GraphEdge graphEdge : this.getGraphEdgeFactory().getEdgesFrom(graphNode.getGraphNodeId()))
			if (!list.contains(graphEdge.getEdgeId()))
				result = result ? this.getGraphEdgeFactory().deleteGraphEdge(graphEdge) : false;

		list.clear();
		
		for (GraphEdge graphEdge : graphNode.getToThisNodeEdges().values()) {
			result = result ? null!=this.getGraphEdgeFactory().saveGraphEdge(graphEdge) : false;
			list.add(graphEdge.getEdgeId());
		}
		for (GraphEdge graphEdge : this.getGraphEdgeFactory().getEdgesTo(graphNode.getGraphNodeId()))
			if (!list.contains(graphEdge.getEdgeId()))
				result = result ? this.getGraphEdgeFactory().deleteGraphEdge(graphEdge) : false;

		return result;
	}

	public Integer saveGraphNode(GraphNode graphNode) {
		Boolean result;
		graphNode.setGraphNodeId(this.saveNode(graphNode));
		result = null != graphNode.getGraphNodeId();

		if (result)
			synchronized (this.getNodeAttributeValueFactory()) {
				try {
					Map<String, String> attrsMap = this.loadGraphNodeAttributeMap(graphNode.getGraphNodeId());
					for (String key : graphNode.getAttributeMap().keySet()) {
						NodeAttributeName nodeAttributeName = this.getNodeAttributeNameFactory().getBy(key); Integer keyId = null == nodeAttributeName ? null : nodeAttributeName.getId().getContent();
						if (null == keyId)
							keyId = this.getAttributeNameId(key);

						if (null != keyId)
							result = null != this.saveAttributeValue(graphNode, keyId, key);
						else
							return null;
						attrsMap.remove(key);
					}

					for (String key : attrsMap.keySet())
						this.getNodeAttributeValueFactory().delete(
								this.getNodeAttributeValueFactory().getBy(graphNode.getGraphNodeId(),
										this.getNodeAttributeNameFactory().getBy(key).getId().getContent()));

				} catch (SQLException e) {
					e.printStackTrace();
					return null;
				}
			}

		this.saveGraphEdges(graphNode);
		return graphNode.getGraphNodeId();
	}

	private Long saveAttributeValue(GraphNode graphNode, Integer keyId, String key) {
		synchronized (this.getNodeAttributeValueFactory()) {
			try {
				if (null == this.getNodeAttributeValueFactory().getBy(graphNode.getGraphNodeId(), keyId))
					return this
							.getNodeAttributeValueFactory()
							.insert(new NodeAttributeValue(new SqlChars<String>((short) 255, graphNode
									.getAttribute(key)),
									new SqlInteger<Integer>((short) 8, graphNode.getGraphNodeId()),
									new SqlInteger<Integer>((short) 8, keyId))).getId().getContent();
				else {
					NodeAttributeValue value = new NodeAttributeValue(new SqlChars<String>((short) 255,
							graphNode.getAttribute(key)), new SqlInteger<Integer>((short) 16,
							graphNode.getGraphNodeId()), new SqlInteger<Integer>((short) 8, keyId));
					value.setId(new SqlInteger<Long>((short) 18, this.getNodeAttributeValueFactory()
							.getBy(graphNode.getGraphNodeId(), keyId).getId().getContent()));
					this.getNodeAttributeValueFactory().update(value);
					return value.getId().getContent();
				}
			} catch (SQLException e) {
				this.getNodeAttributeValueFactory().rollback();
				e.printStackTrace();
				return null;
			}
		}
	}

	public Integer addAttributeName(String name) {
		return this.getAttributeNameId(name);
	}

	private Integer getAttributeNameId(String name) {
		synchronized (this.getNodeAttributeNameFactory()) {
			try {
				return this.getNodeAttributeNameFactory()
						.insert(new NodeAttributeName(new SqlChars<String>((short) 255, name))).getId().getContent();
			} catch (SQLException e) {
				this.getNodeAttributeNameFactory().rollback();
				e.printStackTrace();
				return null;
			}
		}
	}

	private Integer saveNode(GraphNode graphNode) {
		Node node=new Node();
		node.setId(new SqlInteger<Integer>((short)8,graphNode.getGraphNodeId()));
		synchronized (this.getNodeFactory()) {
			try {
				if(null!=this.getNodeFactory().getBean(node.getId()))
					return graphNode.getGraphNodeId();
				else
					return this.getNodeFactory().insert(node).getId().getContent();
			} catch (SQLException e) {
				this.getNodeFactory().rollback();
				e.printStackTrace();
				return null;
			}
		}
	}

	public Map<String, String> loadGraphNodeAttributeMap(Integer graphNodeId) throws SQLException {
		Map<String, String> result = new HashMap<String, String>();
		List<NodeAttributeValue> values = this.getNodeAttributeValueFactory().select(
				"where " + AbstractFactory.NODE_ID + "=" + graphNodeId);
		for (NodeAttributeValue value : values)
			result.put(this.getNodeAttributeNameFactory().getBean(value.getNodeAttributeNameId())
					.getNodeAttributeName().getContent(), value.getNodeAttributeValue().getContent());
		return result;
	}

	public GraphNode getGraphNodeBy(Integer nodeId) throws Exception {
		GraphNode result = new GraphNode();
		result.setGraphNodeId(nodeId);
		try {
			result.setAttributeMap(this.loadGraphNodeAttributeMap(nodeId));
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}

		for (GraphEdge graphEdge : this.getGraphEdgeFactory().getEdgesFrom(nodeId))
			result.putFromThisNodeGraphEdge(graphEdge);

		for (GraphEdge graphEdge : this.getGraphEdgeFactory().getEdgesTo(nodeId))
			result.putToThisNodeGraphEdge(graphEdge);

		return null==result.getGraphNodeId()?null:result;
	}

	public NodeFactory getNodeFactory() {
		return nodeFactory;
	}

	public void setNodeFactory(NodeFactory nodeFactory) {
		this.nodeFactory = nodeFactory;
	}

	public NodeAttributeNameFactory getNodeAttributeNameFactory() {
		return nodeAttributeNameFactory;
	}

	public void setNodeAttributeNameFactory(NodeAttributeNameFactory nodeAttributeNameFactory) {
		this.nodeAttributeNameFactory = nodeAttributeNameFactory;
	}

	public NodeAttributeValueFactory getNodeAttributeValueFactory() {
		return nodeAttributeValueFactory;
	}

	public void setNodeAttributeValueFactory(NodeAttributeValueFactory nodeAttributeValueFactory) {
		this.nodeAttributeValueFactory = nodeAttributeValueFactory;
	}

	public GraphEdgeFactory getGraphEdgeFactory() {
		return graphEdgeFactory;
	}

	public void setGraphEdgeFactory(GraphEdgeFactory graphEdgeFactory) {
		this.graphEdgeFactory = graphEdgeFactory;
	}
}